La couche modèle dans une application orientée objet contient la logique métier, c'est-à-dire la structure des données et tous les calculs, les validations et processus associés aux données.
OpenXava est un framework (cadre de travail) orienté modèle, dans lequel celui-ci est le plus important et tout le reste (entre autre l'interface utilisateur) en dépend. La façon de définir un modèle dans OpenXava est d'utiliser des classes Java pures (même si une version XML est aussi disponible). OpenXava genère une application complète à partir de la définition de votre modèle.
Composant métier
L'unité de base pour créer une application OpenXava est le composant métier. Les composants métier sont définis par une classe Java appelée Entity. Cette classe une entité classique EJB3, en d'autres mots, une classe POJO (Plain Old Java Object) agrémentée d'annotations qui suivent le standard JPA (Java Persistence API).
JPA est le standard Java pour gérer la persistance, soit pour que les objets puissent sauvegarder leur état dans une base de données. Si vous avez déjà développé des applications avec JPA, vous savez déjà développer des applications OpenXava.
En utilisant une simple classe Java, vous pouvez définir un composant métier avec :
le modèle : Structure des données, validateurs, calculs
la vue : La présentation du modèle à l'utilisateur final
la liste : la présentation des données du modèle en mode liste (en mode tableau)
le mapping objet-relationnel : La méthode de stockage et de récupération de l'état de l'objet dans la base de donnée
Ce chapitre explique comme définir la partie modèle, c'est-à-dire tout ce qui concerne la structure, les validateurs, les calculs, etc.
Entity
Pour définir la partie modèle, vous devez déclarer une classe Java avec des annotations. En plus de ses propres annotations, OpenXava utilise les annotations JPA, validateur Hibernate et le Hibernate Annotations. Cette classe java est une entité, soit une classe persistante qui représente un concept métier.
Dans ce chapitre, JPA est utilisé pour indiquer que l'annotation provient du standard de persistance Java, HV pour indiquer qu'elle représente une annotation du validateur Hibernate, HA pour indiquer qu'elle représente une annotation du Hibernate Annotations et OX pour les annotations OpenXava.
Voici la syntaxe pour une entité :
@Entity (JPA, un, requis) : Indique que cette classe est une entité JPA, c'est-à-dire que les instances de cette classe seront persistées.
@EntityValidator (OX, plusieurs, optionnel) : Exécute une validation au niveau du modèle. Le validateur peut recevoir les valeurs de plusieurs propriétés du modèle. Pour valider une propriété unique, il est préféreable d'utiliser un validateur au niveau de la propriété.
@RemoveValidator (OX, plusieurs, optionnel) : S'exécute avant la suppression d'une instance et peut même l'empêcher.
Déclaration de classe : Comme toute classe Java. Vous pouvez utiliser l'héritage (extends) et l'implémentation d'interfaces (implements).
Propriétés : les attributs classiques d'une classe Java. Ils représentent l'état principal de l'objet.
Références : les références à d'autres entités ou classes embarquées.
Collections : collections de références à d'autres entités ou classes embarquées.
Requêtes : les méthodes de requêtes sont des méthodes statiques qui recherchent dans la base de données en utilisant les facilités de JPA.
Méthodes de rappel : Méthodes de rappel contenant la logique métier qui doit être exécuté lors de la création, du chargement, de la modification, de la suppression, etc. de l'instance.
Classes embarquées
La spécification JPA précise : "Une entité peut utiliser d'autres classes à granularité plus fine pour représenter l'état d'une entité. Les instances de ces classes, contrairement aux instances des entités elles-mêmes, ne possèdent pas une identité persistante. Elles existent plutôt comme objets embarqués dans l'entité à laquelle elles appartiennent. De tels objets appartiennent strictement à l'entité propriétaire et ne peuvent être partagées entre plusieurs entités persistées.
La syntaxe d'une classe embarquée est la suivante :
@Embeddable (JPA, unique, requis). Indique que cette classe est une classe embarquée JPA, en d'autres mots, ses instances feront partie d'objets persistants.
Déclaration de classe. Idem que pour une classe Java traditionnelle. Vous pouvez utiliser extends et implements.
Propriétés. Les propriétés traditionnelles de Java
Références. Les références à d'autres entités ou classes embarquées.
Méthodes. Méthodes Java contenant la logique métier.
Propriétés
Une propriété représente un état d'un objet qui peut être lu et parfois mis à jour. L'objet n'a pas l'obligation formelle d'enregistrer physiquement la donnée de la propriété. Il suffit qu'il retourne une valeur lorsque nécessaire.
La syntaxe pour définir une propriété est la suivante :
@Stereotype (OX, optionnel). Spécifie un comportement particulier pour certaines propriétés
@Column(length=) (JPA), @Max (BV, HV), @Length(max=) (HV), @Digits(integer=) (BV), @Digits(integerDigits=) (HV). (optionnel, en général. vous n'utilisez que l'une de ces annotations). Longueur de la propriété en terme de caractères, sauf @Max qui spécifie la longueur maximum. Elles sont très utiles pour la génération des interfaces graphiques. Si vous ne spécifiez pas de longueur, une valeur par défaut est sélectionnée. Celle-ci est associée au type ou stéréotype et récupérée dans le fichier default-size.xml. @Max and @Digits can be from Bean Validation (new in v4.1) or Hibernate Validator (please, translate to French).
@Column(scale=) (JPA), @Digits(fraction=) (BV), @Digits(fractionalDigits=) (HV, optionnel). Échelle de la propriété (taille de la partie décimale). Ne s'applique qu'aux propriétés numériques. If you do not use @Column or @Digits a default value is assumed, if you use @Column without scale a default value is assumed, however if you use @Digits without fraction or fractionalDigits, 0 is assumed (please, translate to French). La valeur par défaut est associée au type ou stéréotype et récupérée dans le fichier default-size.xml. @Digits can be from Bean Validation (new in v4.1) or Hibernate Validator (please, translate to French).
@Required (OX), @Min (HV), @Range(min=) (HV), @Length(min=) (HV) (optionnel, en général. vous n'utilisez que l'une de ces annotations). Indique si la propriété est requise. Dans le cas de @Min, @Range et @Length, vous devez spécifier une valeur plus grande que zéro pour min afin que la propriété soit requise. Par défaut, une propriété et requise pour les clés cachées (depuis v2.1.3) et pas dans les autres cas. Lors de l'enregistrement d'un objet, OpenXava vérifie si les propriétés requises sont présentes. Si ce n'est pas le cas, l'enregistrement n'est pas effectué et une liste d'erreurs de validation est retournée. La logique pour déterminer si une propriété est présente ou non peut être configurée en créant un fichier nommé validators.xml dans votre projet. La syntaxe est accessible dans le fichier OpenXava/xava/default-validators.xml. @Required est une contrainte d'Hibernate Validator (depuis v3.0.1)
@Id (JPA, optionnel). Indique la propriété fait partie de la clé. Au moins une propriété (ou référence) doit être une clé. La combinaison de propriétés clés (et de référence clés) doivent être liées à un groupe de colonnes de la base de données qui n'ont pas de données dupliquées, typiquement les clés primaires.
@Hidden (OX, optionnel). Une propriété cachée comporte un sens pour le développeur, mais pas pour l'utilisateur. Les propriétés cachées sont exclues lors de la génération automatiquement de l'interface graphique. Toutefois, au niveau du code Java, ellse sont présentes et complètement fonctionnelles. Même si vous les spécifiez explicitement dans une vue, elle ne sera pas affichée dans l'interface graphique.
@SearchKey (OX, optionnel). Les propriétés clés de recherche sont utilisées par l'utilisateur comme clés pour rechercher des objets. Elles sont éditable dans l'interface graphique de références permettant à l'utilisateur d'entrer le critère de recherche. OpenXava utilise les propriétés @Id par défaut pour rechercher un objet, mais si celles-ci sont cachées, il utilise la première propriété de la vue. Avec @SearchKey, vous pouvez spécifer explicitement les propriétés pour la recherche.
@Version (JPA, optionnel). Une propriété de version est utilisée pour le contrôle optimiste de concurrence. Si vous souhaitez contrôler la concurrence, vous n'avez besoin que d'annoter une seule propriété avec @Version dans votre entité. Une seule propriété version ne devrait être utilisée par entité. Les types suivants sont valables pour les propriétés de version : int, Integer, short, Short, long, Long, Timestamp. Les propriétés de version sont considérées comme cachées.
@Formula (HA, optionnel): Calcul de la valeur d'une propriété en utilisant la base de données. Obligatoirement un fragment SQL valide.
@DefaultValueCalculator (OX, un seul, optionnel). Implémente la logique pour le calcul de la valeur par défaut (initiale) pour cette propriété. Une propriété annotée avec @DefaultValueCalculator possède un setter et est persistante.
@PropertyValidator (OX, plusieurs, optionnel). Implémente la logique de validation à exécuter sur cette propriété avant de modifier ou créer l'objet qui la contient.
Déclaration de propriété. Une déclaration Java type de propriété avec ces getters et setters. Vous pouvez créer une propriété calculée en spécifiant uniquement un getter sans champs ni setter. Tout type disponible pour JPA est possible, vous n'avez qu'à déterminer le type Hibernate afin de permettre la sauvegarde dans la base de données et une éditeur OpenXava pour le rendu HTML.
Stéréotype
Un stéréotype (@Stereotype) représente une méthode de déterminer le comportement spécifique d'un type. Par exemple, un nom, un commentaire, une description, etc. correspondent tous au type Java java.lang.String, mais vous voudriez sûrement bénéficier de validateurs, de longueurs par défaut, d'éditeurs visuels, etc. différents dans chacun de ces cas et ainsi les définir plus finement. Vous pouvez y arriver en assignant à chacune de ces propriétés un stéréotype. Cela signifie que vous pourriez avoir à disposition les stéréotype NOM, COMMENTAIRE, DESCRIPTION et les associer à vos propriétés.
OpenXava est livré avec les stéréotypes suivants :
DINERO, MONEY (stéréotype monétaire)
FOTO, PHOTO, IMAGEN, IMAGE (stéréotype graphique)
TEXTO_GRANDE, MEMO, TEXT_AREA (stéréotype de zone de texte)
ETIQUETA, LABEL (stéréotype de libellé)
ETIQUETA_NEGRITA, BOLD_LABEL (stéréotype de libellé gras)
A présent, regardons comment définir un stéréotype. Nous allons créer un stéréotype PERSONNE_NOM qui représente le nom de personnes. Ouvrez (ou créez) le fichier editors.xml dans le dossier xava de votre projet et ajoutez l'entrée suivante :
Ainsi, vous définissez l'éditeur à afficher lors de l'édition de propriétés qui répondent au stéréotype PERSONNE_NOM. Ensuite, il est utile d'indiquer la taille par défaut . Vous y parvenez en ajoutant l'entrée suivante dans le fichier default-size.xml de votre projet:
<for-stereotypename="PERSONNE_NOM"size="40"/>
Ainsi, si vous ne spécifiez pas une longueur pour votre propriété du type PERSONNE_NOM, sa taille par défaut sera de 40 unités. Il est plus rare de définir un validateur. Si toutefois vous en avez besoin, vous pouvez ajouter cette entrée dans le fichier validators.xml de votre projet :
Ce validateur s'assure par exemple que la valeur du nom ne contient pas de caractères "blancs" (espace, tabulation, ...).
A présent, tout est prêt pour définir des propriétés de type PERSONNE_NOM :
Dans ce cas, une taille de 40 caractères et un type java.lang.String sont assumés. En outre, le validateur NotBlankCharacterValidator sera exécuté à la vérification si la propriété est requise.
Stéréotype IMAGE_GALLERY
Si vous souhaitez qu'une propriété de votre composant contienne une galerie d'images, vous avez uniquement besoin de déclarer votre propriété avec les stéréotype IMAGE_GALLERY, ainsi :
Ensuite, dans le mapping objet-relationnel, vous devez spécifier une colonne de table capable de stocker une chaîne de caractère (java.lang.String) d'une taille de 32 unités (VARCHAR(32)). Et voilà tout pour votre projet.
Pour utiliser ce stéréotype, vous devez configurer votre système de manière appropriée. Tout d'abord, créez une table dans votre base de données pour stocker les images :
CREATETABLE IMAGES (
ID VARCHAR(32)NOTNULLPRIMARYKEY,
GALLERY VARCHAR(32)NOTNULL,
IMAGE BLOB);
CREATEINDEX IMAGES01
ON IMAGES (GALLERY);
Le type de la colonne IMAGE peut être un autre plus adapté à votre base de donnée pour stocker des tableaux de byte (byte[]), par example LONGVARBINARY. Et finalement, vous devez définir la relation dans le fichier persistence/hibernate.cfg.xml comme ceci:
Après ces étapes, vous pouvez utiliser le stéréotype IMAGES_GALLERY dans tous les composants de votre application.
FILE stereotype (new in v5.0)
Please, translate this section to French
If you want a property of your component to attach a file, you only have to declare your property with the FILE stereotype, in this way:
OpenXava can be configured to store the files in the database or the file system.
Storage in File System
It is the default and requires no additional configuration.
The storage directory is $HOME/oxfiles (GNU/Linux) or %USERPROFILE%\oxfiles (Windows). We can set a different directory using the filesPath property in xava.properties.
Storage in Database
You need three additional settings:
Set the filePersistorClass property to xava.properties
Note that we added <class>org.openxava.web.editors.AttachedFile</class> both persistence units.
When the database is generated, the table OXFILES is created:
CREATETABLE OXFILES (
ID VARCHAR(32)NOTNULLPRIMARYKEY,
NAME VARCHAR(255),DATA LONGVARBINARY,
LIBRARYID VARCHAR(32));
We check that the type of DATA column is the most suitable type for storing byte[] (in our case LONGVARBINARY).
Concurrence et version des propriétés
La concurrence est la capacité du système a permettre à plusieurs utilisateurs d'enregistrer des données simultanément sans perte. OpenXava utilise la concurrence optimiste de JPA. En utilisant ce type de concurrence, les enregistrements ne sont pas bloqués tout en permettant une haute concurrence sans impacter l'intégrité des données.
Par exemple, si un utilisateur A consulte un enregistrement et que l'utilisateur B ouvre celui-ci, le modifie et l'enregistre entre-temps, lorsque l'utilisateur A essaie d'enregistrer les données, il recevra une erreur lui demandant de rafraîchir les informations avant d'essayer d'enregistrer à nouveau avec ses modifications. Pour activer la gestion de la concurrence pour votre entité, il suffit de déclarer une propriété avec l'annotation @Version, comme ceci :
Cette propriété est utilisée par le moteur de persistance et ne devrait pas être manipulée directement par votre application ou par les utilisateurs.
Enums
OpenXava permet d'utiliser les enums tels que définis dans Java 5. Un enum vous permet de spécifier une propriété qui n'accepte que certains valeurs prédéfinies. C'est facile à utiliser comme l'exemple ci-après :
private Distance distance;publicenum Distance { LOCAL, NATIONAL, INTERNATIONAL };
La propriété distance n'accepte que les valeurs suivantes : LOCAL, NATIONAL, INTERNATIONAL et comme l'annotation @Required n'est pas présente, pas de valeur (null) est aussi possible.
Au niveau de l'interface utilisateur, l'implémentation actuelle utilise une liste déroulante de choix. Le libellé de chaque valeur est obtenu dans les fichiers i18n. Au niveau de la base de donnée, la valeur est enregistrée par défaut comme un entier (dans notre cas, 0 pour LOCAL, 1 pour NATIONAL, 2 pour INTERNATIONAL et null pour pas de valeur). Il est toutefois simple de définir un autre type et travailler avec des base de données légataires. Plus d'informations peuvent être obtenues dans le chapitre consacré au mapping objet relationnel.
Propriétés calculées
Les propriétés calculées sont toujours en lecture seule (elle n'ont qu'un getter) et ne sont pas persistantes (elle ne correspondent à aucune colonne de table de base de données). Une propriété calculée est définie comme ceci:
Selon la définition ci-dessus, vous pouvez utiliser le code ainsi :
Product product = ...
product.setUnitPrice(2);BigDecimal result = product.getUnitPriceInPesetas();
Et result contiendra 332.772.
Lorsque la propriété unitPriceInPesetas est affichée pour l'utilisateur, elle ne sera pas modifiable et son éditeur aura une taille de 10, indiquée par l'annotation @Max(9999999999L) (2). En outre, puisque @Depends("unitPrice") est spécifié (1), lorsque l'utilisateur changera la valeur de la propriété unitPrice dans l'interface utilisateur, la propriété unitPriceInPesetas sera recalculée et sa nouvelle valeur rafraîchie pour l'utilisateur.
A partir d'une propriété calculée, vous pouvez avoir accès directement aux connexions JDBC. En voici un exemple :
@Max(999)publicint getDetailsCount(){// An example of using JDBCConnection con = null;try{
con = DataSourceConnectionProvider.getByComponent("Invoice").getConnection();// 1String table = MetaModel.get("InvoiceDetail").getMapping().getTable();PreparedStatement ps = con.prepareStatement("select count(*) from " + table +
" where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
ps.setInt(1, getYear());
ps.setInt(2, getNumber());ResultSet rs = ps.executeQuery();
rs.next();Integer result = newInteger(rs.getInt(1));
ps.close();return result;}catch(Exception ex){
log.error("Problem calculating details count of an Invoice", ex);// You can throw any runtime exception herethrownewSystemException(ex);}finally{try{
con.close();}catch(Exception ex){}}}
Oui, le code JDBC n'est pas très propre et joli, mais parfois, il peut aider dans des problèmes de performance. La classe DataSourceConnectionProvider vous permet la connexion associée à la même source de donnée que celle indiquée dans votre entité (Invoice dans notre cas). Cette classe peut être utilisée à votre convenance, mais vous pouvez accéder à une connexion JDBC en utilisant JNDI ou une autre méthode de votre choix. En fait, dans une propriété calculée, vous pouvez écrire n'importe quelle code que Java vous permet.
Si vous utilisez l'accès sur la base des propriétés, c'est-à-dire que vous annotez les getters et setters de votre classe, vous devez ajouter l'annotation @Transient à votre propriété calculée, comme ceci :
privatelong number;
@Id @Column(length=10)// Vous annotez le getter,publiclong getNumber(){// afin que JPA utilise un accès à base de propriété pour votre classereturn number;}publicvoid setNumber(long number){this.number = number;}
@Transient // Vous devez annotez votre propriété calculée avec @TransientpublicString getZoneOne(){// parce que vous utilisez l'accès à base de propriétéreturn"In ZONE 1";}
Formula (depuis v3.1.4)
En utilisant @Formulades annotations Hibernate, vous pouvez définir un calcul de votre propriété. Ce calcul est exprimé avec le langage SQL et est exécuté par la base de données, et non par Java. Vous n'avez besoin que d'écrire un fragment SQL valide comme ici:
L'utilisation est simple. Ecrivez votre calcule exactement de la même manière que vous le feriez dans un ordre SQL.
En général, les propriétés anotées avec @Formula sont en lecture seule, c'est-à-dire qu'elles bénéficient d'un getter et pas de setter. Lorsque l'objet est lu de la base de données, le calcul est executé par la base de données et la propriété prend la valeur retournée.
Cette option est une alternative au propriétés calculées. Elle a l'avantage que l'utilisateur peut la filtrer dans le mode liste et le désavantage que vous devez utiliser SQL au lieu de Java et vous ne pouvez pas utiliser @Depends pour le calcul dynamiques de la valeur
Calculateur de la valeur par défaut
Avec l'annotation @DefaultValueCalculator vous pouvez associer de la logique à votre propriété, dans le cas où la propriété est en lecture/écriture. Ce calculateur est destiné à calculer la valeur initiale d'une propriété. Par exemple :
Dans ce cas, lorsque l'utilisateur essaye de créer une nouvelle facture (par exemple), il constatera que la valeur du champ année (year) est déjà définie, et qu'il peut la changer si besoin. La logique pour générer cette valeur initiale se trouve dans la classe CurrentYearCalculator que voici :
packageorg.openxava.calculators;importjava.util.*;/**
* @author Javier Paniza
*/publicclass CurrentYearCalculator implements ICalculator {publicObject calculate()throwsException{Calendar cal = Calendar.getInstance();
cal.setTime(new java.util.Date());returnnewInteger(cal.get(Calendar.YEAR));}}
Il est possible de personnaliser le comportement d'un calculateur en spécifiant les valeurs de ses propriétés, comme ici :
Dans cet exemple, pour calculer la valeur initiale, OpenXava crée une instance de StringCalculator et injecte la valeur BONNE dans la propriété string de StringCalculator avant d'appeler la méthode execute() qui initialise la valeur par défaut de la propriété relationwithSeller (relation avec le vendeur). Comme vous le voyez, l'utilisation de l'annotation @PropertyValue vous permet de créer des calculateurs réutilisables. @PropertyValue vous permet d'injecter des valeurs issues d'autres propriétés de cette façon :
Dans ce cas, avant de lancer l'exécution du calculateur, OpenXava remplit la propriété drivingLicenceType (type de permis de conduire) de CarrierRemarksCalculator (calculateur de remarques sur le transporteur) avec la valeur affichée de la propriété type de la référence drivingLicence (permis de conduire). Comme vous le constatez, l'attribut from permet l'utilisation de propriété qualifiées (référence.propriété).
Vous pouvez aussi utilisez @PropertyValue sans from ni value :
Ci-dessus, OpenXava prend la valeur de la propriété affichée familyNumber (numéro de famille) et l'injecte dans la propriété familyNumber du calculateur. Ceci signifie que @PropertyValue(name="familyNumber) est équivalent à @PropertyValue(name="familyNumber" from="familyNumber"). Depuis un calculateur, vous avez un accès direct aux connexions JDBC. En voici un exemple :
Afin de pouvoir utiliser JDBC, votre calculateur doit implémenter l'interface IJDBCCalculator (1) afin qu'il puisse recevoir une instance de IConnectionProvider (2) utilisable dans la méthode calculate().
OpenXava est livré avec une série de calculateurs prédéfinis dans le paquet org.openxava.calculators.
Valeurs par défaut à la création
Vous pouvez indiquer qu'une valeur doit être calculée juste avant la création d'une entité (enregistrement dans la base de données) pour la première fois. En général, pour les clés, il est conseillé d'utiliser JPA. Par exemple, si vous voulez utiliser une colonne d'identité (auto-incrémentée) comme clé :
Consultez la section 9.1.9 de la spécification JPA 1.0 (qui fait partie de la recommandation JSR-220) pour apprendre plus sur @GeneratedValue. Si vous souhaitez utiliser votre propre logique pour générer une valeur à la création, ou que vous souhaitez une valeur générée pour une propriété ne faisant pas partie de la clé, vous ne pouvez pas utiliser l'annotation @GeneratedValue. Il est toutefois simple de régler ces cas avec JPA. Il faut simplement ajouter ce code à votre à votre classe :
L'annotation JPA @PrePersist fait en sorte que cette méthode est exécutée avant d'insérer la première fois l'instance dans la base de données. Dans cette méthode, vous pouvez calculer avec votre propre logique les valeurs de vos propriétés qu'elles fassent ou non partie de la clé.
Validateur de propriété
L'annotation @PropertyValidator exécute la logique de validation de la valeur assignée à la propriété juste avant son enregistrement. Une propriété peut avoir plusieurs validateurs :
La technique pour configurer un validateur (avec @PropertyValue, though from attribute does not work, you must use value always) est exactement la même que pour les calculateurs. Avec l'attribut onlyOnCreate=true vous pouvez spécifier si le validateur ne s'exécute qu'à la création de l'objet et pas lorsque celui-ci est modifié.
Le code du validateur est le suivant :
Un validateur doit implémenter l'interface IPropertyValidator (A) ce qui oblige votre validateur à posséder une méthode validate() dans laquelle la validation de la propriété est exécutée. Les arguments de la méthode validate() sont les suivants :
Messages errors. Un objet de type Messages qui représente une suite de messages (comme une collection intelligente) à laquelle vous pouvez ajouter vos erreurs de validation
Object value. La valeur à valider
String objectName. Le nom de l'objet contenant la propriété à valider. Utile pour les messages d'erreur
String propertyName. Le nom de la propriété à valider. Utile pour les messages d'erreur
Comme vous le voyez, si votre code rencontre une erreur de validation, vous devez la signaler en ajoutant un identificateur de message et les arguments (avec errors.add()). Si vous souhaitez avoir un message significatif, vous pouvez ajouter dans vos fichier i18n la définition suivante :
exclude_string={0} ne peut contenir {2} dans {1}
Si l'identificateur de message ajouté n'est pas trouvé dans les fichiers de ressource, l'identificateur est affiché tel quel. Toutefois, il est recommandé de toujours utiliser les fichier de ressources i18n.
La validation est considérée comme réussie si aucun message n'a été ajouté et ratée si un ou plusieurs messages ont été ajoutés. OpenXava rassemble tous les messages de tous les validateurs avant l'enregistrement et s'il y a des messages, les affiche après avoir annulé l'enregistrement. Please, translate to French: Since v4.6.1 is also possible to use in the validator the message of @PropertyValidator. That is, you can write:
Please, translate to French: If the message is between braces is get from i18n files, if not is used as is.
Moreover, you have to implement the IWithMessage interface in your validator:
publicclass BookTitleValidator implements IPropertyValidator, IWithMessage {privateString message;publicvoid setMessage(String message)throwsException{this.message = message;// This message is from @PropertyValidator}publicvoid validate(Messages errors, Object value, String propertyName, String modelName){if(((String)value).contains("RPG")){
errors.add(message);// You can add it directly}}}
Please, translate to French: The message specified in the @PropertyValidator annotation, rpg_book_not_allowed, is injected in the validator calling setMessage(). This message can be added directly as an error.
Le paquet org.openxava.validators contient des validateurs génériques. @PropertyValidator est défini comme une contrainte d'Hibernate Validator (depuis v3.0.1) Please, translate to French: If you need to use JPA in your validator, please see Using JPA from a Validator or Callback.
Validateur par défaut (depuis v2.0.3)
Vous pouvez définir un validateur de propriété par défaut dépendant de son type ou stéréotype. Pour ce faire, vous avez besoin d'utiliser le fichier xava/validators.xml de votre projet pour y définir les validateurs par défaut.
Par exemple, vous pouvez définir le fichier xava/validators.xml ainsi :
Ainsi, vous associez le validateur PersonNameValidator au stéréotype PERSONNE_NOM. A présent, vous pouvez définir une propriété d'entité comme la suivante :
Cette propriété sera validée en utilisant le validateur PersonNameValidator même si la propriété elle-même ne définit pas de validateur. PersonNameValidator est appliqué à toutes les propriétés qui portent le stéréotype PERSONNE_NOM. Il est également possible d'associer un validateur à un type.
Dans validators.xml, vous avez aussi le loisir de déclarer des validateurs déterminant si une valeur requise est présente (exécutés lorsque l'annotation @Required est présente). En outre, vous pouvez définir des noms (alias) pour les classes de validateurs.
Pour en savoir plus sur les validateurs, examinez les fichiers OpenXava/xava/default-validators.xml et OpenXavaTest/xava/validators.xml. Les validateurs par défaut ne sont pas pris en compte lorsque vous utilisez l'API JPA pour enregistrer vos objets.
Références
Une référence vous permet d'accéder à une entité depuis une autre entité. Une référence est traduite dans le code Java comme une propriété (avec son getter et son setter) dont le type est le modèle Java référencé. Par exemple, un Client peut contenir une référence vers son Vendeur, ce qui nous permet d'écrire le code pour accéder au nom du Vendeur de ce Client ainsi :
Client client = ...
client.getVendeur().getNom();
La syntaxe d'une référence est définie comme suit:
@ManyToOne(optional=false) (JPA), @Required (OX) (optionnel, celui de JPA est conseillé). Indique si la référence est requise ou non. Lors de l'enregistrement, OpenXava vérifie que les références requise sont présentes, sinon il annule l'opération et une liste d'erreurs est retournée.
@Id (JPA, optionnel). Indique si la référence fait partie de la clé. La combinaison de propriétés et de références clés doivent correspondre à un ensemble de colonnes de la base de données avec des valeurs uniques, typiquement des clés primaires.
@SearchKey (OX, un seul, optionnel) (depuis v3.0.2). Les références clés de recherche sont employées par l'utilisateur comme clé pour chercher des objets. Elles sont éditable dans l'interface utilisateur de la référence permettant à l'utilisateur d'entrer une valeur de recherche. OpenXava utilise les membres @Id par défaut pour la recherche et si ceux-ci sont cachés, il utilise la première propriété visible dans la vue. Avec @SearchKey, vous pouvez explicitement choisir des référence pour la recherche.
@DefaultValueCalculator (OX, un seul, optionnel). Implémente la logique pour calculer la valeur initiale de la référence. Le calculateur doit retourner la valeur de la clé qui peut être une valeur simple (seulement si la clé de l'objet référencé est simple) ou une objet clé (un objet spécial qui enrobe une combinaison de clés).
Déclaration de référence. Une référence traditionnelle Java avec ses getter et setter. La référence est marquée avec //@ManyToOne// (JPA) et le type doit être une autre entité.
Une référence nommée vendeur vers une entité Vendeur
Une référence nommée vendeurAlternatif vers une entité Vendeur. Dans ce cas, nous utilisons fetch=FetchType.LAZY pour que la donnée soit lue à partir de la base de donnée uniquement à la demande. Ceci est une approche plus efficace, mais n'est pas le standard JPA, c'est pourquoi il est conseillé de toujours utiliser fetch=FetchType.LAZY dans la déclaration de références.
Si nous considérons que le code ci-dessus fait partie d'une entité Client, nous pourrions écrire :
Calculateur de la valeur par défaut dans les références
Dans le cadre d'une référence, @DefaultValueCalculator travaille de la même manière que pour une propriété. La seule différence, c'est que le calculateur doit retourner la clé de la référence. Par exemple, dans le cas d'une référence avec clé simple, vous pouvez écrire :
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="FAMILY")
@DefaultValueCalculator(value=IntegerCalculator.class, properties=
@PropertyValue(name="value", value="2"))private Family family;
La méthode calculate() de la classe IntegerCalculator est définie ainsi :
Comme vous le voyez, un objet java.lang.Integer est retourné. Cela signifie que la valeur par défaut pour family vaut 2.
Dans le cas d'une clé composée :
Le code du calculateur retourne un objet de type Warehouse:
packageorg.openxava.test.calculators;importorg.openxava.calculators.*;/**
* @author Javier Paniza
*/publicclass DefaultWarehouseCalculator implements ICalculator {publicObject calculate()throwsException{
Warehouse key = new Warehouse();
key.setNumber(4);
key.setZoneNumber(4);return key;}}
Utilisation de références comme clés
Vous pouvez utiliser les référence comme clés, ou partie de la clé, de votre entité. Vous devez annoter votre référence avec @Id et utiliser une classe d'identification de la clé comme ici :
@Entity
@IdClass(AdditionalDetailKey.class)publicclass AdditionalDetail {// JoinColumn is also specified in AditionalDetailKey because// a bug in Hibernate, see http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Id @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")private Service service;
@Id @Hidden
privateint counter;
...
}
Et la classe d'identification de la clé :
publicclass AdditionalDetailKey implements java.io.Serializable{
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")private Service service;
@Hidden
privateint counter;// equals, hashCode, toString, getters and setters
...
}
Vous devez écrire la classe d'identification de clé même si la clé était uniquement la référence avec seulement une colonne de jointure. Il est conseillé de n'utiliser cette méthode qu'en travaillant avec des bases de données existantes. Si vous travaillez avec votre propre schéma de base de données, utilisez plutôt les identifiants auto-générés.
Références embarquées
Voici un exemple d'une propriété Address embarquée (avec l'annotation @Embedded) référencée par l'entité principale. Dans l'entité principale vous écrivez :
@Embedded
private Address address;
Et vous devez définir la classe Address comme embarquable :
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;/**
*
* @author Javier Paniza
*/
@Embeddable
publicclass Address implements IWithCity {// 1
@Required @Column(length=30)privateString street;
@Required @Column(length=5)privateint zipCode;
@Required @Column(length=20)privateString city;// ManyToOne dans un Embeddable n'est pas supporté par JPA 1.0 (voir 9.1.34),// mais l'implémentation d'Hibernate le supporte.
@ManyToOne(fetch=FetchType.LAZY, optional=false) @JoinColumn(name="STATE")privateState state;// 2publicString getCity(){return city;}publicvoid setCity(String city){this.city = city;}publicString getStreet(){return street;}publicvoid setStreet(String street){this.street = street;}publicint getZipCode(){return zipCode;}publicvoid setZipCode(int zipCode){this.zipCode = zipCode;}publicState getState(){return state;}publicvoid setState(State state){this.state = state;}}
Comme montré ci-dessus, une classe embarquable peut implémenter une interface (1) et contenir des références (2) entre autres choses. Toutefois, il ne peut utiliser les méthodes de rappel JPA.
Le code ci-dessus peut être utilisé ainsi, pour la lecture des objets :
Customer customer = ...
Address address = customer.getAddress();
address.getStreet();// pour obtenir la valeur
Ou de cette façon pour spécifier une adresse (définie par l'objet Address) :
// éfinition d'une nouvelle adresse
Address address = new Address();
address.setStreet(“Ma rue”);
address.setZipCode(1007);
address.setCity(“Lausanne”);
address.setState(state);
customer.setAddress(address);
Dans cet exemple, vous avez une référence simple (pas de collection) et le code généré correspond à un JavaBean dont le cycle de vie est associé à l'entité qui le possède, c'est-à-dire que l'instance Address est supprimée et créée au travers de la classe Customer. Une instance d'Address n'aura jamais une vie propre et ne peut pas être partagée avec d'autres instances de Customer.
Collections
Collections de entités
Il est possible de définir une collection de références à des entités. Une collection est une propriété Java de type java.util.Collection. En voici la syntaxe :
@Size (BV, HV, optionnel). Nombre minimal (min) ou maximum (max) d'éléments attendus. Ceci est validé juste avant l'enregistrement.
@Condition (OX, optionnel). Restreint les éléments qui font partie de la collection.
@OrderBy (JPA, optionnel). Les éléments de la collection seront définis selon cet ordre.
@XOrderBy (OX, optionnel). @OrderBy de JPA ne permet pas d'utiliser des propriétés qualifiées (propriétés de références). @XOrderBy compense ce manque.
Déclaration de collection. Une déclaration Java ordinaire d'une collection avec ses getter et setter. La collection est marquée avec @ManyToOne (JPA) ou @ManyToMany (JPA) et le type doit être une autre entité.
Si ce code est présent dans une classe Invoice (facture), vous définissez donc une collection de deliveries (livraisons) associée à cette facture. Les détails qui définissent cette relation sont expliqués dans le chapitre dédié au mapping objet-relationnel. Vous utilisez mappedBy="invoice" pour indiquer que la référence invoice de la classe Delivery est utilisée pour lier cette collection.
A présent, vous pouvez écrire le code suivant pour faire quelque-chose avec les livraisons (deliveries) associée à cette facture :
Utiliser REMOVE comme type de cascade a pour effet de supprimer tous les détails associés lorsque l'utilisateur supprimer l'objet Invoice.
Avec @OrderBy, vous forcez l'ordre des détails dans la collection selon la propriété serviceType (type de service) du détail.
La restriction @Size(min=1) force le fait qu'un objet Invoice doit contenir au moins un détail pour être valide.
Vous avez toute la liberté de définir comment les données de la collection sont obtenues. Avec @Condition, vous pouvez écraser les conditions par défaut :
@Condition("${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND " +
"${warehouse.number} = ${this.warehouse.number} AND " +
"NOT (${number} = ${this.number})")publicCollection<Carrier> getFellowCarriers(){returnnull;}
Si vous avez cette collection dans un objet Carrier (transporteur), vous pouvez obtenir tous les transporteurs liés au même entrepôt (Warehouse) sauf lui-même, soit tous les transporteurs concurrents. Comme vous pouvez le constater, vous pouvez utiliser le mot-clé this dans la condition afin de référencer la valeur de la propriété de l'objet courant. @Condition est uniquement appliqué dans le cadre de l'interface graphique générée automatiquement par OpenXava. Si vous faites appel directement à la méthode getFellowCarriers(), elle retournera toujours null.
Si cela ne vous suffit pas, vous pouvez écrire la logique qui retourne la collection. L'exemple précédant peut aussi être écrit de la manière suivante :
publicCollection<Carrier> getFellowCarriers(){Query query = XPersistence.getManager().createQuery("from Carrier c where " +
"c.warehouse.zoneNumber = :zone AND " +
"c.warehouse.number = :warehouseNumber AND " +
"NOT (c.number = :number) ");
query.setParameter("zone", getWarehouse().getZoneNumber());
query.setParameter("warehouseNumber", getWarehouse().getNumber());
query.setParameter("number", getNumber());return query.getResultList();}
Comme vous le voyez, c'est une méthode getter traditionnelle. Trivialement, c'est une méthode qui retourne un objet de type java.util.Collection dont les éléments sont de type Carrier.
Les références dans les collections sont bidirectionnelles. Ceci signifie que si un Vendeur possède des clients, le Client doit avoir une référence vers le Vendeur. Mais il est possible que dans Client, il existe plusieurs références à Vendeur (par exemple vendeur et vendeurAlternatif). JPA ne sachant pas lequel choisir, il faut donc utiliser l'attribut mappedBy de @OneToMany. Voici une manière de l'utiliser pour indiquer que c'est vendeur et non vendeurAlternatif qui est référencé dans cette collection :
Dans ce cas, un client possède plusieurs états, et un état peut être utilisé par plusieurs clients.
Collections embarquées
Les collections embarquées ne sont pas prises en charge par JPA 1.0, mais vous pouvez les simuler en utilisant des collections d'entités avec les type de cascade REMOVE ou ALL. OpenXava gère ces collections d'une manière spéciale, comme si c'était des collections embarquées. A présent, voyons un exemple de collections embarquées en commençant par l'entité principale :
Ceci est une entité complexe, avec des validateurs, des calculateurs, des références, etc. Il a fallu également définir une référence à l'entité propriétaire (invoice). Dans le cas ci-dessus, lorsqu'une facture (instance d'Invoice) est supprimée, tous ses détails le sont aussi. En outre, il existe des différences au niveau de l'interface graphique dont vous pouvez apprendre plus en consultant le chapitre Vue.
Element collections (new in v5.0)
Please, translate this section to French
Since JPA 2.0 you can define a collection of real embeddable objects. We call these collections element collections.
This is the syntax for element collections:
@Size (BV, HV, optional): Minimum (min) and/or maximum (max) number of expected elements. This is validated just before saving.
@OrderBy (JPA, optional): The elements in collections will be in the indicated order.
Collection declaration: A regular Java collection declaration with its getters and setters. The collection is marked with @ElementCollection (JPA). The elements must be embeddable classes.
The elements in the collection are saved all at once at the same time of the main entity. Moreover, the generated user interface allows the user to modify all the elements of the collection at the same time.
An embeddable class that is contained within an element collection must not contain collections of any type.
Let's see an example. First you have to define the collection in the main entity:
As you can see, an embeddable class used in an element collection can contain references(1), validations(2) and calculated properties(3) among other things.
Méthodes
Les méthodes sont définies dans une entité OpenXava (en réalité, une entité JPA) comme dans une classe Java classique. Par exemple :
Les méthodes sont la sauce des objets. Sans elles, une entité ne serait qu'une bête structure de données. Lorsque c'est possible, il est toujours conseillé de placer la logique métier dans des méthodes (couche modèle) plutôt que dans des actions (couche contrôleur).
Requêtes
Les requêtes sont des méthodes Java statiques qui vous permettent de chercher un ou plusieurs objets qui répondent à des critères, comme dans ces exemples :
publicstatic Customer findByNumber(int number)throws NoResultException {Query query = XPersistence.getManager().createQuery("from Customer as o where o.number = :number");
query.setParameter("number", number);return(Customer) query.getSingleResult();}publicstaticCollection findAll(){Query query = XPersistence.getManager().createQuery("from Customer as o");return query.getResultList();}publicstaticCollection findByNameLike(String name){Query query = XPersistence.getManager().createQuery("from Customer as o where o.name like :name order by o.name desc");
query.setParameter("name", name);return query.getResultList();}
L'utilisation des méthodes de recherche rendent le code beaucoup plus lisible qu'avec la syntaxe "bavarde" des requêtes JPA. Mais ceci n'est qu'une recommandation de style, car vous pouvez choisir d'utiliser les requêtes JPA plutôt que ces méthodes.
Validateur d'entité
Une annotation @EntityValidator permet de définir une validation au niveau de l'entité. Lorsque vous avez besoin d'exécuter une validation sur plusieurs propriétés en même temps, et que cette validation ne correspond logiquement à aucune de celle-là, vous pouvez utiliser ce type de validation dont la syntaxe est :
value (obligatoire). La classe qui implémente la logique de validation. Elle doit implémenter l'interface IValidator.
onlyOnCreate (optionnel). Si vrai (true), le validateur n'est exécuté qu'à la création de l'objet et non lorsqu'un objet existant est modifié. La valeur par défaut est faux (false)
properties (plusieurs @PropertyValue, optionnel). Permet de déclarer les valeurs des propriétés du validateur avant de l'exécuter.
Un validateur doit implémenter l'interface IValidator (1) ce qui force l'écriture de la méthode validate(Messages messages). Dans cette méthode, les identifiants des messages d'erreurs (ceux déclarés dans les fichiers i18n) sont ajoutés. Si le processus entier de validation (soit l'exécution de tous les validateurs) produit au moins un message d'erreur, OpenXava interrompt l'opération d'enregistrement et informe l'utilisateur des erreurs.
Dans cet exemple, vous pouvez voir que les propriétés description et unitPrice sont utilisés dans la validation, c'est pourquoi le validateur est placé au niveau du modèle et non des propriétés individuelles, car le cadre de la validation dépasse une seule propriété. Please, translate to French: Since v4.6.1 the validator can implement IWithMessage to inject the message from @EntityValidator, it works like in the property validator case.
Il est possible d'appliquer plusieurs validateurs d'entité à une entité, comme ceci :
L'annotation @RemoveValidator est également un validateur au niveau du modèle, tout comme l'annotation@EntityValidator. Mais dans ce cas, la validation est exécutée juste avant la suppression d'un objet et elle a la possibilité d'annuler cette opération. La syntaxe est la suivante :
packageorg.openxava.test.validators;importorg.openxava.test.model.*;importorg.openxava.util.*;importorg.openxava.validators.*;/**
* @author Javier Paniza
*/publicclass DeliveryTypeRemoveValidator implements IRemoveValidator {// 1private DeliveryType deliveryType;privateint number;// We use this (instaed of obtaining it from deliveryType)// for testing @PropertyValue for simple propertiespublicvoid setEntity(Object entity)throwsException{// 2this.deliveryType = (DeliveryType) entity;}publicvoid validate(Messages errors)throwsException{if(!deliveryType.getDeliveries().isEmpty()){
errors.add("not_remove_delivery_type_if_in_deliveries", newInteger(getNumber()));// 3}}publicint getNumber(){return number;}publicvoid setNumber(int number){this.number = number;}}
Le validateur implémente l'interface IRemoveValidator (1) ce qui force la présence de la méthode setEntity() qui reçoit en paramètre l'objet à supprimer. Si une erreur de validation est ajoutée à l'objet Messages envoyé à la méthode validate(), la validation de suppression échoue. Si, après avoir exécuté toutes les validations, des erreurs de validation existent, OpenXava annule la suppression de l'objet et informe l'utilisateur avec la liste des erreurs.
Dans l'exemple, le validateur vérifie s'il y a des livraisons (deliveries) qui utilisent ce type de livraison (DeliveryType) avant de supprimer celui-ci.
Comme dans le cas de l'annotation @EntityValidator, il est possible de déclarer plusieurs annotations @RemoveValidator en utilisant l'annotation @RemoveValidators. Les validateurs de suppression sont exécutés lors de la suppressions des objets par OpenXava (depuis MapFacade ou avec les actions standards), mais pas en utilisant directement l'API JPA. Si vous souhaitez appliquer une restriction en utilisant l'API JPA, utilisez l'annotation @PreRemove devant votre méthode.
Méthodes de rappel JPA
Avec l'annotation @PrePersist, vous pouvez injecter votre propre logique exécutée juste avant le premier enregistrement de l'objet (que son état devienne persistant dans la base de donnée). Dans cet exemple, chaque fois qu'un type de livraison (DeliveryType) est créé, une description est ajoutée :
Comme vous pouvez le constater, la méthode est identique aux autres méthodes, sauf qu'elle exécutée automatiquement juste avant la création de l'objet dans la base de données.
Avec l'annotation @PreUpdate, vous pouvez de la même manière injecter votre logique à exécuter avant l'enregistrement de la mise à jour de l'état de votre objet, c'est-à-dire juste avant l'exécution de l'ordre UPDATE dans la base de données. Dans cet exemple, chaque fois qu'un type de livraison est modifié, un suffixe est ajouté à la description :
Comme vous pouvez le constater, la méthode est identique aux autres méthodes, sauf qu'elle exécutée automatiquement juste avant la modification de l'objet dans la base de données.
Toutes les annotations JPA sont disponibles dans OpenXava : @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate et @PostLoad
Méthodes de rappel OX (à partir de la V4.0.1)
Please, translate this section to French
En utilisant @PreCreate vous pouvez définir une logique qui sera exécutée avant la persistance de l'objet. Vous pouvez ainsi effectuer des opérations de gestion des entités ou des requêtes qui ne sont pas autorisées dans les méthodes de rappel JPA.
Par exemple, si nous voulons créer un client et l'affecter à une facture au cas où aucun client n'aurait été spécifié :
@PreCreate
publicvoid onPreCreate(){// Création automatique d'un nouveau clientif(getCustomer() == null){
Customer cust = new Customer();
cust.setName(getName());
cust.setAddress(getAddress());
cust = XPersistence.getManager().merge(cust);
setCustomer(cust);}}
L'opération de gestion de l'entité n'affectera pas le comportement de la méthode de rappel. Tous comme avec @PreCreate, les méthodes possédant les annotations @PostCreate et @PreDelete font partie de la même transaction, ce qui fait que l'utilisation de ces annotations conserve l'intégrité de l'information sans aucun effort supplémentaire de la part du développeur. Combinée aux annotations JPA, l'ordre d'exécution de chaque méthode de rappel est le suivant :
Pour la création d'une entité : @PreCreate, @PrePersist(JPA), @PostPersist(JPA) et @PostCreate.
Pour la suppression d'une entité : @PreDelete, @PreRemove(JPA), @PostRemove(JPA).
Les méthodes possédant ces annotations ne doivent retourner aucune valeur ni accepter aucun paramètre. Ces annotations ont été prévues pour les entités, et sont ignorées lorsqu'utilisées sur les listeners d'entité (entity listeners).
Héritage
OpenXava supporte les héritages Java et JPA. Par exemple, vous pouvez définir une classe annotée avec @MappedSuperclass ainsi :
packageorg.openxava.test.model;importjavax.persistence.*;importorg.hibernate.annotations.*;importorg.openxava.annotations.*;/**
* Classe de base pour les entités identifiées à l'aide d'un UUID. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
publicclass Identifiable {
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")privateString oid;publicString getOid(){return oid;}publicvoid setOid(String oid){this.oid = oid;}}
Vous pouvez définir une autre classe annotée @MappedSuperclass qui étend celle ci-dessus :
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;/**
* Classe de base pour les entités ayant une propriété nom (name). <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
publicclass Nameable extends Identifiable {
@Column(length=50) @Required
privateString name;publicString getName(){return name;}publicvoid setName(String name){this.name = name;}}
A partir de maintenant, vous pouvez utiliser les classe Identifiable et Nameable pour définir vos entités, comme ici:
packageorg.openxava.test.model;importjavax.persistence.*;/**
*
* @author Javier Paniza
*/
@Entity
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue("HUM")
@Table(name="PERSON")
@AttributeOverrides(
@AttributeOverride(name="name", column=@Column(name="PNAME")))publicclass Human extends Nameable {
@Enumerated(EnumType.STRING)private Sex sex;publicenum Sex { MALE, FEMALE };public Sex getSex(){return sex;}publicvoid setSex(Sex sex){this.sex = sex;}}
Il est également possible de faire un réel héritage d'une entité vers une autre entité :
Vous pouvez créer un module OpenXava pour les classe Human et Programmer, mais pas Identifiable et Nameable directement. Dans le module Programmer, l'utilisateur n'a accès qu'aux instances de Programmer. Par contre, dans le module Human, l'utilisateur aura accès aux instances de Human. et de Programmer. En outre, si l'utilisateur essaye de voir le détail d'une instance de Programmer depuis le module Human, la vue Programmer sera affichée. Du vrai polymorphisme. Please, translate to French: Since v4.5 OpenXava supports all inheritance features of JPA, including single table per class hierarchy, joined and table per class mapping strategies, before v4.5 only @AttributeOverrides and single table per class hierarchy mapping strategy was supported.
Clés composites
La méthode conseillée pour définir la clé d'une entité est d'utiliser un identifiant unique autgénéré (avec les annotations @Id et @GeneratedValue), mais parfois, par exemple en travaillant avec des base de données existantes, vous vous trouvez confronté à une entité mise en relation avec une table qui utilise plusieurs colonnes comme clé. Ce cas peut être résolu avec JPA (et donc OpenXava) de deux manières, soit avec l'annotation @IdClass ou avec l'annotation @EmbeddedId.
Classe d'identification
Si vous utilisez l'annotation @IdClass dans votre entité pour indiquer une classe clé , vous devez marquer les propriétés clés avec l'annotation @Id :
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;importorg.openxava.jpa.*;/**
*
* @author Javier Paniza
*/
@Entity
@IdClass(WarehouseKey.class)publicclass Warehouse {
@Id
// La colonne esr également spécifiée dans la classe WarehouseKey à cause d'un bogue dans Hibernate, cf// http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Column(length=3, name="ZONE")privateint zoneNumber;
@Id @Column(length=3)privateint number;
@Column(length=40) @Required
privateString name;publicString getName(){return name;}publicvoid setName(String name){this.name = name;}publicint getNumber(){return number;}publicvoid setNumber(int number){this.number = number;}publicint getZoneNumber(){return zoneNumber;}publicvoid setZoneNumber(int zoneNumber){this.zoneNumber = zoneNumber;}}
Vous devez également déclarer une classe d'identification, une classe régulière sérialisable contenant toutes les propriétés clés de votre entité :
Please, translate this section to French
OpenXava has full support for the standard Java for Bean Validation JSR-303. You can define your own constraints for your entities as explained in Bean Validation specification, and OpenXava will recognize them, showing the corresponding validation messages to the user. Consult the latest Hibernate Validator documentation to learn how to write a JSR-303 validator, since the current version of Hibernate Validator implements JSR-303.
Read the article in JavaLobby about using JSR-303 with OpenXava.
@AssertTrue
Since v4.9 OpenXava allows to inject properties and qualified properties (reference properties) that belong to validated bean, into the message identified by means of the message element of @AssertTrue. Example:
In this case we have an @AssertTrue annotating a field of the entity:
{not_roadworthy} is the message identifier that is declared under i18n file like this:
not_roadworthy={type} plate {licensePlate} is not roadworthy. It can not be assigned to the driver {driver.name}
If we have an entity: type=AUTO, licensePlate=A1-0001 and roadworthy=false, and try to assign driver (name = MIGUEL GRAU), the validation method will fail and display the error message:
AUTO plate A1-0001 is not roadworthy. It can not be assigned to the driver MIGUEL GRAU
Validateur Hibernate (depuis v3.0.1)
OpenXava supporte complétement le validateur Hibernate . Vous pouvez définir vos propres contraintes pour vos entités comme expliqué dans la documentation du validateur Hibernate et OpenXava les reconnaîtra en montrant les messages de validations correspondant à l'utilisateur.
En outre, les annotations OpenXava @Required , @PropertyValidator et @EntityValidator sont définies comme des contraintes du validateur Hibernate, ce qui signifie que si vous enregitrez une objet directement avec JPA, ces contraintes s'appliqueront. D'un autre côté, les annotations @RemoveValidator, @PropertyValidator(onlyOnCreate=true), @EntityValidator(onlyOnCreate=true) et la validation par défaut d'OpenXava ne sont pas reconnues par le validateur Hibernate ou JPA, mais seulement par OpenXava.
Table of Contents
Chapitre 3
La couche modèle dans une application orientée objet contient la logique métier, c'est-à-dire la structure des données et tous les calculs, les validations et processus associés aux données.OpenXava est un framework (cadre de travail) orienté modèle, dans lequel celui-ci est le plus important et tout le reste (entre autre l'interface utilisateur) en dépend. La façon de définir un modèle dans OpenXava est d'utiliser des classes Java pures (même si une version XML est aussi disponible). OpenXava genère une application complète à partir de la définition de votre modèle.
Composant métier
L'unité de base pour créer une application OpenXava est le composant métier. Les composants métier sont définis par une classe Java appelée Entity. Cette classe une entité classique EJB3, en d'autres mots, une classe POJO (Plain Old Java Object) agrémentée d'annotations qui suivent le standard JPA (Java Persistence API).JPA est le standard Java pour gérer la persistance, soit pour que les objets puissent sauvegarder leur état dans une base de données. Si vous avez déjà développé des applications avec JPA, vous savez déjà développer des applications OpenXava.
En utilisant une simple classe Java, vous pouvez définir un composant métier avec :
- le modèle : Structure des données, validateurs, calculs
- la vue : La présentation du modèle à l'utilisateur final
- la liste : la présentation des données du modèle en mode liste (en mode tableau)
- le mapping objet-relationnel : La méthode de stockage et de récupération de l'état de l'objet dans la base de donnée
Ce chapitre explique comme définir la partie modèle, c'est-à-dire tout ce qui concerne la structure, les validateurs, les calculs, etc.Entity
Pour définir la partie modèle, vous devez déclarer une classe Java avec des annotations. En plus de ses propres annotations, OpenXava utilise les annotations JPA, validateur Hibernate et le Hibernate Annotations. Cette classe java est une entité, soit une classe persistante qui représente un concept métier.Dans ce chapitre, JPA est utilisé pour indiquer que l'annotation provient du standard de persistance Java, HV pour indiquer qu'elle représente une annotation du validateur Hibernate, HA pour indiquer qu'elle représente une annotation du Hibernate Annotations et OX pour les annotations OpenXava.
Voici la syntaxe pour une entité :
Classes embarquées
La spécification JPA précise :"Une entité peut utiliser d'autres classes à granularité plus fine pour représenter l'état d'une entité. Les instances de ces classes, contrairement aux instances des entités elles-mêmes, ne possèdent pas une identité persistante. Elles existent plutôt comme objets embarqués dans l'entité à laquelle elles appartiennent. De tels objets appartiennent strictement à l'entité propriétaire et ne peuvent être partagées entre plusieurs entités persistées.
La syntaxe d'une classe embarquée est la suivante :
Propriétés
Une propriété représente un état d'un objet qui peut être lu et parfois mis à jour. L'objet n'a pas l'obligation formelle d'enregistrer physiquement la donnée de la propriété. Il suffit qu'il retourne une valeur lorsque nécessaire.La syntaxe pour définir une propriété est la suivante :
Stéréotype
Un stéréotype (@Stereotype) représente une méthode de déterminer le comportement spécifique d'un type. Par exemple, un nom, un commentaire, une description, etc. correspondent tous au type Java java.lang.String, mais vous voudriez sûrement bénéficier de validateurs, de longueurs par défaut, d'éditeurs visuels, etc. différents dans chacun de ces cas et ainsi les définir plus finement. Vous pouvez y arriver en assignant à chacune de ces propriétés un stéréotype. Cela signifie que vous pourriez avoir à disposition les stéréotype NOM, COMMENTAIRE, DESCRIPTION et les associer à vos propriétés.OpenXava est livré avec les stéréotypes suivants :
- DINERO, MONEY (stéréotype monétaire)
- FOTO, PHOTO, IMAGEN, IMAGE (stéréotype graphique)
- TEXTO_GRANDE, MEMO, TEXT_AREA (stéréotype de zone de texte)
- ETIQUETA, LABEL (stéréotype de libellé)
- ETIQUETA_NEGRITA, BOLD_LABEL (stéréotype de libellé gras)
- HORA, TIME (stéréotype de temps)
- FECHAHORA, DATETIME (stéréotype de date et temps)
- GALERIA_IMAGENES, IMAGES_GALLERY (stéréotype de galerie d'images) (instructions de mise oeuvre)
- RELLENADO_CON_CEROS, ZEROS_FILLED (stéréotype de nombre avec remplissage de zéros)
- TEXTO_HTML, HTML_TEXT (stéréotype de texte avec format éditable)
- IMAGE_LABEL, ETIQUETA_IMAGEN (stéréotype d'image dépendante du contenu de la propriété)
- EMAIL (stéréotype d'adresse de messagerie)
- TELEFONO, TELEPHONE (stéréotype de numéro de téléphone)
- WEBURL (stéréotype d'adresse Internet - URL)
- IP (stéréotype d'adresse IP)
- ISBN (stéréotype de numéro ISBN pour les livres)
- TARJETA_CREDITO, CREDIT_CARD (stéréotype pour les numéros de cartes de crédit)
- LISTA_EMAIL, EMAIL_LIST (stéréotype pour les listes d'adresses de messagerie)
- DOCUMENT_LIBRARY, LIBRERIA_DOCUMENTOS (depuis v4m6, please translate to French: it only works inside Liferay)
- PASSWORD, CONTRASENA (depuis v4.1)
- MAC (depuis v4.8)
- FILE, ARCHIVO (depuis v5.0) (instructions de mise oeuvre)
A présent, regardons comment définir un stéréotype. Nous allons créer un stéréotype PERSONNE_NOM qui représente le nom de personnes. Ouvrez (ou créez) le fichier editors.xml dans le dossier xava de votre projet et ajoutez l'entrée suivante :Ainsi, vous définissez l'éditeur à afficher lors de l'édition de propriétés qui répondent au stéréotype PERSONNE_NOM. Ensuite, il est utile d'indiquer la taille par défaut . Vous y parvenez en ajoutant l'entrée suivante dans le fichier default-size.xml de votre projet:
<for-stereotype name="PERSONNE_NOM" size="40"/>Ainsi, si vous ne spécifiez pas une longueur pour votre propriété du type PERSONNE_NOM, sa taille par défaut sera de 40 unités. Il est plus rare de définir un validateur. Si toutefois vous en avez besoin, vous pouvez ajouter cette entrée dans le fichier validators.xml de votre projet :Ce validateur s'assure par exemple que la valeur du nom ne contient pas de caractères "blancs" (espace, tabulation, ...).
A présent, tout est prêt pour définir des propriétés de type PERSONNE_NOM :
Dans ce cas, une taille de 40 caractères et un type java.lang.String sont assumés. En outre, le validateur NotBlankCharacterValidator sera exécuté à la vérification si la propriété est requise.
Stéréotype IMAGE_GALLERY
Si vous souhaitez qu'une propriété de votre composant contienne une galerie d'images, vous avez uniquement besoin de déclarer votre propriété avec les stéréotype IMAGE_GALLERY, ainsi :Ensuite, dans le mapping objet-relationnel, vous devez spécifier une colonne de table capable de stocker une chaîne de caractère (java.lang.String) d'une taille de 32 unités (VARCHAR(32)). Et voilà tout pour votre projet.
Pour utiliser ce stéréotype, vous devez configurer votre système de manière appropriée. Tout d'abord, créez une table dans votre base de données pour stocker les images :
Le type de la colonne IMAGE peut être un autre plus adapté à votre base de donnée pour stocker des tableaux de byte (byte[]), par example LONGVARBINARY. Et finalement, vous devez définir la relation dans le fichier persistence/hibernate.cfg.xml comme ceci:
Après ces étapes, vous pouvez utiliser le stéréotype IMAGES_GALLERY dans tous les composants de votre application.
FILE stereotype (new in v5.0)
Please, translate this section to FrenchIf you want a property of your component to attach a file, you only have to declare your property with the FILE stereotype, in this way:
OpenXava can be configured to store the files in the database or the file system.
Storage in File System
It is the default and requires no additional configuration.The storage directory is $HOME/oxfiles (GNU/Linux) or %USERPROFILE%\oxfiles (Windows). We can set a different directory using the filesPath property in xava.properties.
Storage in Database
You need three additional settings:- Modify the persistence.xml
Note that we added <class>org.openxava.web.editors.AttachedFile</class> both persistence units.- When the database is generated, the table OXFILES is created:
We check that the type of DATA column is the most suitable type for storing byte[] (in our case LONGVARBINARY).Concurrence et version des propriétés
La concurrence est la capacité du système a permettre à plusieurs utilisateurs d'enregistrer des données simultanément sans perte. OpenXava utilise la concurrence optimiste de JPA. En utilisant ce type de concurrence, les enregistrements ne sont pas bloqués tout en permettant une haute concurrence sans impacter l'intégrité des données.Par exemple, si un utilisateur A consulte un enregistrement et que l'utilisateur B ouvre celui-ci, le modifie et l'enregistre entre-temps, lorsque l'utilisateur A essaie d'enregistrer les données, il recevra une erreur lui demandant de rafraîchir les informations avant d'essayer d'enregistrer à nouveau avec ses modifications. Pour activer la gestion de la concurrence pour votre entité, il suffit de déclarer une propriété avec l'annotation @Version, comme ceci :
Cette propriété est utilisée par le moteur de persistance et ne devrait pas être manipulée directement par votre application ou par les utilisateurs.
Enums
OpenXava permet d'utiliser les enums tels que définis dans Java 5. Un enum vous permet de spécifier une propriété qui n'accepte que certains valeurs prédéfinies. C'est facile à utiliser comme l'exemple ci-après :La propriété distance n'accepte que les valeurs suivantes : LOCAL, NATIONAL, INTERNATIONAL et comme l'annotation @Required n'est pas présente, pas de valeur (null) est aussi possible.
Au niveau de l'interface utilisateur, l'implémentation actuelle utilise une liste déroulante de choix. Le libellé de chaque valeur est obtenu dans les fichiers i18n. Au niveau de la base de donnée, la valeur est enregistrée par défaut comme un entier (dans notre cas, 0 pour LOCAL, 1 pour NATIONAL, 2 pour INTERNATIONAL et null pour pas de valeur). Il est toutefois simple de définir un autre type et travailler avec des base de données légataires. Plus d'informations peuvent être obtenues dans le chapitre consacré au mapping objet relationnel.
Propriétés calculées
Les propriétés calculées sont toujours en lecture seule (elle n'ont qu'un getter) et ne sont pas persistantes (elle ne correspondent à aucune colonne de table de base de données). Une propriété calculée est définie comme ceci:Selon la définition ci-dessus, vous pouvez utiliser le code ainsi :
Et result contiendra 332.772.
Lorsque la propriété unitPriceInPesetas est affichée pour l'utilisateur, elle ne sera pas modifiable et son éditeur aura une taille de 10, indiquée par l'annotation @Max(9999999999L) (2). En outre, puisque @Depends("unitPrice") est spécifié (1), lorsque l'utilisateur changera la valeur de la propriété unitPrice dans l'interface utilisateur, la propriété unitPriceInPesetas sera recalculée et sa nouvelle valeur rafraîchie pour l'utilisateur.
A partir d'une propriété calculée, vous pouvez avoir accès directement aux connexions JDBC. En voici un exemple :
Oui, le code JDBC n'est pas très propre et joli, mais parfois, il peut aider dans des problèmes de performance. La classe DataSourceConnectionProvider vous permet la connexion associée à la même source de donnée que celle indiquée dans votre entité (Invoice dans notre cas). Cette classe peut être utilisée à votre convenance, mais vous pouvez accéder à une connexion JDBC en utilisant JNDI ou une autre méthode de votre choix. En fait, dans une propriété calculée, vous pouvez écrire n'importe quelle code que Java vous permet.
Si vous utilisez l'accès sur la base des propriétés, c'est-à-dire que vous annotez les getters et setters de votre classe, vous devez ajouter l'annotation @Transient à votre propriété calculée, comme ceci :
Formula (depuis v3.1.4)
En utilisant @Formulades annotations Hibernate, vous pouvez définir un calcul de votre propriété. Ce calcul est exprimé avec le langage SQL et est exécuté par la base de données, et non par Java. Vous n'avez besoin que d'écrire un fragment SQL valide comme ici:L'utilisation est simple. Ecrivez votre calcule exactement de la même manière que vous le feriez dans un ordre SQL.
En général, les propriétés anotées avec @Formula sont en lecture seule, c'est-à-dire qu'elles bénéficient d'un getter et pas de setter. Lorsque l'objet est lu de la base de données, le calcul est executé par la base de données et la propriété prend la valeur retournée.
Cette option est une alternative au propriétés calculées. Elle a l'avantage que l'utilisateur peut la filtrer dans le mode liste et le désavantage que vous devez utiliser SQL au lieu de Java et vous ne pouvez pas utiliser @Depends pour le calcul dynamiques de la valeur
Calculateur de la valeur par défaut
Avec l'annotation @DefaultValueCalculator vous pouvez associer de la logique à votre propriété, dans le cas où la propriété est en lecture/écriture. Ce calculateur est destiné à calculer la valeur initiale d'une propriété. Par exemple :Dans ce cas, lorsque l'utilisateur essaye de créer une nouvelle facture (par exemple), il constatera que la valeur du champ année (year) est déjà définie, et qu'il peut la changer si besoin. La logique pour générer cette valeur initiale se trouve dans la classe CurrentYearCalculator que voici :
Il est possible de personnaliser le comportement d'un calculateur en spécifiant les valeurs de ses propriétés, comme ici :
Dans cet exemple, pour calculer la valeur initiale, OpenXava crée une instance de StringCalculator et injecte la valeur BONNE dans la propriété string de StringCalculator avant d'appeler la méthode execute() qui initialise la valeur par défaut de la propriété relationwithSeller (relation avec le vendeur). Comme vous le voyez, l'utilisation de l'annotation @PropertyValue vous permet de créer des calculateurs réutilisables. @PropertyValue vous permet d'injecter des valeurs issues d'autres propriétés de cette façon :
Dans ce cas, avant de lancer l'exécution du calculateur, OpenXava remplit la propriété drivingLicenceType (type de permis de conduire) de CarrierRemarksCalculator (calculateur de remarques sur le transporteur) avec la valeur affichée de la propriété type de la référence drivingLicence (permis de conduire). Comme vous le constatez, l'attribut from permet l'utilisation de propriété qualifiées (référence.propriété).
Vous pouvez aussi utilisez @PropertyValue sans from ni value :
Ci-dessus, OpenXava prend la valeur de la propriété affichée familyNumber (numéro de famille) et l'injecte dans la propriété familyNumber du calculateur. Ceci signifie que @PropertyValue(name="familyNumber) est équivalent à @PropertyValue(name="familyNumber" from="familyNumber"). Depuis un calculateur, vous avez un accès direct aux connexions JDBC. En voici un exemple :
Et la classe du calculateur :
Afin de pouvoir utiliser JDBC, votre calculateur doit implémenter l'interface IJDBCCalculator (1) afin qu'il puisse recevoir une instance de IConnectionProvider (2) utilisable dans la méthode calculate().
OpenXava est livré avec une série de calculateurs prédéfinis dans le paquet org.openxava.calculators.
Valeurs par défaut à la création
Vous pouvez indiquer qu'une valeur doit être calculée juste avant la création d'une entité (enregistrement dans la base de données) pour la première fois. En général, pour les clés, il est conseillé d'utiliser JPA. Par exemple, si vous voulez utiliser une colonne d'identité (auto-incrémentée) comme clé :Vous pouvez utiliser d'autres méthodes de génération. Par exemple, une séquence de base de données peut être définie avec la méthode standard de JPA :
Si vous souhaitez générer une chaîne de caractères (de type java.lang.String) d'une longueur de 32, vous disposez des extensions Hibernate de JPA :
Consultez la section 9.1.9 de la spécification JPA 1.0 (qui fait partie de la recommandation JSR-220) pour apprendre plus sur @GeneratedValue. Si vous souhaitez utiliser votre propre logique pour générer une valeur à la création, ou que vous souhaitez une valeur générée pour une propriété ne faisant pas partie de la clé, vous ne pouvez pas utiliser l'annotation @GeneratedValue. Il est toutefois simple de régler ces cas avec JPA. Il faut simplement ajouter ce code à votre à votre classe :
L'annotation JPA @PrePersist fait en sorte que cette méthode est exécutée avant d'insérer la première fois l'instance dans la base de données. Dans cette méthode, vous pouvez calculer avec votre propre logique les valeurs de vos propriétés qu'elles fassent ou non partie de la clé.
Validateur de propriété
L'annotation @PropertyValidator exécute la logique de validation de la valeur assignée à la propriété juste avant son enregistrement. Une propriété peut avoir plusieurs validateurs :La technique pour configurer un validateur (avec @PropertyValue, though from attribute does not work, you must use value always) est exactement la même que pour les calculateurs. Avec l'attribut onlyOnCreate=true vous pouvez spécifier si le validateur ne s'exécute qu'à la création de l'objet et pas lorsque celui-ci est modifié.
Le code du validateur est le suivant :
Un validateur doit implémenter l'interface IPropertyValidator (A) ce qui oblige votre validateur à posséder une méthode validate() dans laquelle la validation de la propriété est exécutée. Les arguments de la méthode validate() sont les suivants :
- Messages errors. Un objet de type Messages qui représente une suite de messages (comme une collection intelligente) à laquelle vous pouvez ajouter vos erreurs de validation
- Object value. La valeur à valider
- String objectName. Le nom de l'objet contenant la propriété à valider. Utile pour les messages d'erreur
- String propertyName. Le nom de la propriété à valider. Utile pour les messages d'erreur
Comme vous le voyez, si votre code rencontre une erreur de validation, vous devez la signaler en ajoutant un identificateur de message et les arguments (avec errors.add()). Si vous souhaitez avoir un message significatif, vous pouvez ajouter dans vos fichier i18n la définition suivante :exclude_string={0} ne peut contenir {2} dans {1}Si l'identificateur de message ajouté n'est pas trouvé dans les fichiers de ressource, l'identificateur est affiché tel quel. Toutefois, il est recommandé de toujours utiliser les fichier de ressources i18n.La validation est considérée comme réussie si aucun message n'a été ajouté et ratée si un ou plusieurs messages ont été ajoutés. OpenXava rassemble tous les messages de tous les validateurs avant l'enregistrement et s'il y a des messages, les affiche après avoir annulé l'enregistrement.
Please, translate to French: Since v4.6.1 is also possible to use in the validator the message of @PropertyValidator. That is, you can write:
Please, translate to French: If the message is between braces is get from i18n files, if not is used as is.
Moreover, you have to implement the IWithMessage interface in your validator:
Please, translate to French: The message specified in the @PropertyValidator annotation, rpg_book_not_allowed, is injected in the validator calling setMessage(). This message can be added directly as an error.
Le paquet org.openxava.validators contient des validateurs génériques. @PropertyValidator est défini comme une contrainte d'Hibernate Validator (depuis v3.0.1)
Please, translate to French: If you need to use JPA in your validator, please see Using JPA from a Validator or Callback.
Validateur par défaut (depuis v2.0.3)
Vous pouvez définir un validateur de propriété par défaut dépendant de son type ou stéréotype. Pour ce faire, vous avez besoin d'utiliser le fichier xava/validators.xml de votre projet pour y définir les validateurs par défaut.Par exemple, vous pouvez définir le fichier xava/validators.xml ainsi :
Ainsi, vous associez le validateur PersonNameValidator au stéréotype PERSONNE_NOM. A présent, vous pouvez définir une propriété d'entité comme la suivante :
Cette propriété sera validée en utilisant le validateur PersonNameValidator même si la propriété elle-même ne définit pas de validateur. PersonNameValidator est appliqué à toutes les propriétés qui portent le stéréotype PERSONNE_NOM. Il est également possible d'associer un validateur à un type.
Dans validators.xml, vous avez aussi le loisir de déclarer des validateurs déterminant si une valeur requise est présente (exécutés lorsque l'annotation @Required est présente). En outre, vous pouvez définir des noms (alias) pour les classes de validateurs.
Pour en savoir plus sur les validateurs, examinez les fichiers OpenXava/xava/default-validators.xml et OpenXavaTest/xava/validators.xml. Les validateurs par défaut ne sont pas pris en compte lorsque vous utilisez l'API JPA pour enregistrer vos objets.
Références
Une référence vous permet d'accéder à une entité depuis une autre entité. Une référence est traduite dans le code Java comme une propriété (avec son getter et son setter) dont le type est le modèle Java référencé. Par exemple, un Client peut contenir une référence vers son Vendeur, ce qui nous permet d'écrire le code pour accéder au nom du Vendeur de ce Client ainsi :La syntaxe d'une référence est définie comme suit:
- @ManyToOne(optional=false) (JPA), @Required (OX) (optionnel, celui de JPA est conseillé). Indique si la référence est requise ou non. Lors de l'enregistrement, OpenXava vérifie que les références requise sont présentes, sinon il annule l'opération et une liste d'erreurs est retournée.
- @Id (JPA, optionnel). Indique si la référence fait partie de la clé. La combinaison de propriétés et de références clés doivent correspondre à un ensemble de colonnes de la base de données avec des valeurs uniques, typiquement des clés primaires.
- @SearchKey (OX, un seul, optionnel) (depuis v3.0.2). Les références clés de recherche sont employées par l'utilisateur comme clé pour chercher des objets. Elles sont éditable dans l'interface utilisateur de la référence permettant à l'utilisateur d'entrer une valeur de recherche. OpenXava utilise les membres @Id par défaut pour la recherche et si ceux-ci sont cachés, il utilise la première propriété visible dans la vue. Avec @SearchKey, vous pouvez explicitement choisir des référence pour la recherche.
- @DefaultValueCalculator (OX, un seul, optionnel). Implémente la logique pour calculer la valeur initiale de la référence. Le calculateur doit retourner la valeur de la clé qui peut être une valeur simple (seulement si la clé de l'objet référencé est simple) ou une objet clé (un objet spécial qui enrobe une combinaison de clés).
- Déclaration de référence. Une référence traditionnelle Java avec ses getter et setter. La référence est marquée avec //@ManyToOne// (JPA) et le type doit être une autre entité.
Voici un petit exemple de références :- Une référence nommée vendeur vers une entité Vendeur
- Une référence nommée vendeurAlternatif vers une entité Vendeur. Dans ce cas, nous utilisons fetch=FetchType.LAZY pour que la donnée soit lue à partir de la base de donnée uniquement à la demande. Ceci est une approche plus efficace, mais n'est pas le standard JPA, c'est pourquoi il est conseillé de toujours utiliser fetch=FetchType.LAZY dans la déclaration de références.
Si nous considérons que le code ci-dessus fait partie d'une entité Client, nous pourrions écrire :Calculateur de la valeur par défaut dans les références
Dans le cadre d'une référence, @DefaultValueCalculator travaille de la même manière que pour une propriété. La seule différence, c'est que le calculateur doit retourner la clé de la référence. Par exemple, dans le cas d'une référence avec clé simple, vous pouvez écrire :La méthode calculate() de la classe IntegerCalculator est définie ainsi :
Comme vous le voyez, un objet java.lang.Integer est retourné. Cela signifie que la valeur par défaut pour family vaut 2.
Dans le cas d'une clé composée :
Le code du calculateur retourne un objet de type Warehouse:
Utilisation de références comme clés
Vous pouvez utiliser les référence comme clés, ou partie de la clé, de votre entité. Vous devez annoter votre référence avec @Id et utiliser une classe d'identification de la clé comme ici :Et la classe d'identification de la clé :
Vous devez écrire la classe d'identification de clé même si la clé était uniquement la référence avec seulement une colonne de jointure. Il est conseillé de n'utiliser cette méthode qu'en travaillant avec des bases de données existantes. Si vous travaillez avec votre propre schéma de base de données, utilisez plutôt les identifiants auto-générés.
Références embarquées
Voici un exemple d'une propriété Address embarquée (avec l'annotation @Embedded) référencée par l'entité principale. Dans l'entité principale vous écrivez :Et vous devez définir la classe Address comme embarquable :
Comme montré ci-dessus, une classe embarquable peut implémenter une interface (1) et contenir des références (2) entre autres choses. Toutefois, il ne peut utiliser les méthodes de rappel JPA.
Le code ci-dessus peut être utilisé ainsi, pour la lecture des objets :
Ou de cette façon pour spécifier une adresse (définie par l'objet Address) :
Dans cet exemple, vous avez une référence simple (pas de collection) et le code généré correspond à un JavaBean dont le cycle de vie est associé à l'entité qui le possède, c'est-à-dire que l'instance Address est supprimée et créée au travers de la classe Customer. Une instance d'Address n'aura jamais une vie propre et ne peut pas être partagée avec d'autres instances de Customer.
Collections
Collections de entités
Il est possible de définir une collection de références à des entités. Une collection est une propriété Java de type java.util.Collection. En voici la syntaxe :- @Size (BV, HV, optionnel). Nombre minimal (min) ou maximum (max) d'éléments attendus. Ceci est validé juste avant l'enregistrement.
- @Condition (OX, optionnel). Restreint les éléments qui font partie de la collection.
- @OrderBy (JPA, optionnel). Les éléments de la collection seront définis selon cet ordre.
- @XOrderBy (OX, optionnel). @OrderBy de JPA ne permet pas d'utiliser des propriétés qualifiées (propriétés de références). @XOrderBy compense ce manque.
- Déclaration de collection. Une déclaration Java ordinaire d'une collection avec ses getter et setter. La collection est marquée avec @ManyToOne (JPA) ou @ManyToMany (JPA) et le type doit être une autre entité.
Voyons quelques exemples :Si ce code est présent dans une classe Invoice (facture), vous définissez donc une collection de deliveries (livraisons) associée à cette facture. Les détails qui définissent cette relation sont expliqués dans le chapitre dédié au mapping objet-relationnel. Vous utilisez mappedBy="invoice" pour indiquer que la référence invoice de la classe Delivery est utilisée pour lier cette collection.
A présent, vous pouvez écrire le code suivant pour faire quelque-chose avec les livraisons (deliveries) associée à cette facture :
Voyons à présent une exemple un peu plus complexe toujours dans la classe Invoice :
- Utiliser REMOVE comme type de cascade a pour effet de supprimer tous les détails associés lorsque l'utilisateur supprimer l'objet Invoice.
- Avec @OrderBy, vous forcez l'ordre des détails dans la collection selon la propriété serviceType (type de service) du détail.
- La restriction @Size(min=1) force le fait qu'un objet Invoice doit contenir au moins un détail pour être valide.
Vous avez toute la liberté de définir comment les données de la collection sont obtenues. Avec @Condition, vous pouvez écraser les conditions par défaut :Si vous avez cette collection dans un objet Carrier (transporteur), vous pouvez obtenir tous les transporteurs liés au même entrepôt (Warehouse) sauf lui-même, soit tous les transporteurs concurrents. Comme vous pouvez le constater, vous pouvez utiliser le mot-clé this dans la condition afin de référencer la valeur de la propriété de l'objet courant. @Condition est uniquement appliqué dans le cadre de l'interface graphique générée automatiquement par OpenXava. Si vous faites appel directement à la méthode getFellowCarriers(), elle retournera toujours null.
Si cela ne vous suffit pas, vous pouvez écrire la logique qui retourne la collection. L'exemple précédant peut aussi être écrit de la manière suivante :
Comme vous le voyez, c'est une méthode getter traditionnelle. Trivialement, c'est une méthode qui retourne un objet de type java.util.Collection dont les éléments sont de type Carrier.
Les références dans les collections sont bidirectionnelles. Ceci signifie que si un Vendeur possède des clients, le Client doit avoir une référence vers le Vendeur. Mais il est possible que dans Client, il existe plusieurs références à Vendeur (par exemple vendeur et vendeurAlternatif). JPA ne sachant pas lequel choisir, il faut donc utiliser l'attribut mappedBy de @OneToMany. Voici une manière de l'utiliser pour indiquer que c'est vendeur et non vendeurAlternatif qui est référencé dans cette collection :
L'annotation @ManyToMany (JPA) permet de définir une collection avec une multiplicité de plusieurs à plusieurs, comme ici :
Dans ce cas, un client possède plusieurs états, et un état peut être utilisé par plusieurs clients.
Collections embarquées
Les collections embarquées ne sont pas prises en charge par JPA 1.0, mais vous pouvez les simuler en utilisant des collections d'entités avec les type de cascade REMOVE ou ALL. OpenXava gère ces collections d'une manière spéciale, comme si c'était des collections embarquées. A présent, voyons un exemple de collections embarquées en commençant par l'entité principale :Notez que CascadeType.REMOVE est utilisé et que InvoiceDetail est une entité et pas une classe embarquable :
Ceci est une entité complexe, avec des validateurs, des calculateurs, des références, etc. Il a fallu également définir une référence à l'entité propriétaire (invoice). Dans le cas ci-dessus, lorsqu'une facture (instance d'Invoice) est supprimée, tous ses détails le sont aussi. En outre, il existe des différences au niveau de l'interface graphique dont vous pouvez apprendre plus en consultant le chapitre Vue.
Element collections (new in v5.0)
Please, translate this section to FrenchSince JPA 2.0 you can define a collection of real embeddable objects. We call these collections element collections.
This is the syntax for element collections:
- @Size (BV, HV, optional): Minimum (min) and/or maximum (max) number of expected elements. This is validated just before saving.
- @OrderBy (JPA, optional): The elements in collections will be in the indicated order.
- Collection declaration: A regular Java collection declaration with its getters and setters. The collection is marked with @ElementCollection (JPA). The elements must be embeddable classes.
The elements in the collection are saved all at once at the same time of the main entity. Moreover, the generated user interface allows the user to modify all the elements of the collection at the same time.An embeddable class that is contained within an element collection must not contain collections of any type.
Let's see an example. First you have to define the collection in the main entity:
Then define your embeddable class:
As you can see, an embeddable class used in an element collection can contain references(1), validations(2) and calculated properties(3) among other things.
Méthodes
Les méthodes sont définies dans une entité OpenXava (en réalité, une entité JPA) comme dans une classe Java classique. Par exemple :Les méthodes sont la sauce des objets. Sans elles, une entité ne serait qu'une bête structure de données. Lorsque c'est possible, il est toujours conseillé de placer la logique métier dans des méthodes (couche modèle) plutôt que dans des actions (couche contrôleur).
Requêtes
Les requêtes sont des méthodes Java statiques qui vous permettent de chercher un ou plusieurs objets qui répondent à des critères, comme dans ces exemples :Voici comment utiliser ces méthodes :
L'utilisation des méthodes de recherche rendent le code beaucoup plus lisible qu'avec la syntaxe "bavarde" des requêtes JPA. Mais ceci n'est qu'une recommandation de style, car vous pouvez choisir d'utiliser les requêtes JPA plutôt que ces méthodes.
Validateur d'entité
Une annotation @EntityValidator permet de définir une validation au niveau de l'entité. Lorsque vous avez besoin d'exécuter une validation sur plusieurs propriétés en même temps, et que cette validation ne correspond logiquement à aucune de celle-là, vous pouvez utiliser ce type de validation dont la syntaxe est :- value (obligatoire). La classe qui implémente la logique de validation. Elle doit implémenter l'interface IValidator.
- onlyOnCreate (optionnel). Si vrai (true), le validateur n'est exécuté qu'à la création de l'objet et non lorsqu'un objet existant est modifié. La valeur par défaut est faux (false)
- properties (plusieurs @PropertyValue, optionnel). Permet de déclarer les valeurs des propriétés du validateur avant de l'exécuter.
Un exemple :Et le code du validateur :
Un validateur doit implémenter l'interface IValidator (1) ce qui force l'écriture de la méthode validate(Messages messages). Dans cette méthode, les identifiants des messages d'erreurs (ceux déclarés dans les fichiers i18n) sont ajoutés. Si le processus entier de validation (soit l'exécution de tous les validateurs) produit au moins un message d'erreur, OpenXava interrompt l'opération d'enregistrement et informe l'utilisateur des erreurs.
Dans cet exemple, vous pouvez voir que les propriétés description et unitPrice sont utilisés dans la validation, c'est pourquoi le validateur est placé au niveau du modèle et non des propriétés individuelles, car le cadre de la validation dépasse une seule propriété.
Please, translate to French: Since v4.6.1 the validator can implement IWithMessage to inject the message from @EntityValidator, it works like in the property validator case.
Il est possible d'appliquer plusieurs validateurs d'entité à une entité, comme ceci :
@EntityValidator est défini comme une contrainte d'Hibernate Validator (depuis v3.0.1)
Please, translate to French: If you need to use JPA in your validator, please see Using JPA from a Validator or Callback.
Validateur de suppression
L'annotation @RemoveValidator est également un validateur au niveau du modèle, tout comme l'annotation@EntityValidator. Mais dans ce cas, la validation est exécutée juste avant la suppression d'un objet et elle a la possibilité d'annuler cette opération. La syntaxe est la suivante :- value (requis). La classe qui implémente la logique de validation. Cette classe doit implémenter l'interface IRemoveValidator
- properties (plusieurs @PropertyValue, optionnel). Permet de déclarer les valeurs des propriétés du validateur avant de l'exécuter.
Un possible exemple :Le validateur est défini comme ceci :
Le validateur implémente l'interface IRemoveValidator (1) ce qui force la présence de la méthode setEntity() qui reçoit en paramètre l'objet à supprimer. Si une erreur de validation est ajoutée à l'objet Messages envoyé à la méthode validate(), la validation de suppression échoue. Si, après avoir exécuté toutes les validations, des erreurs de validation existent, OpenXava annule la suppression de l'objet et informe l'utilisateur avec la liste des erreurs.
Dans l'exemple, le validateur vérifie s'il y a des livraisons (deliveries) qui utilisent ce type de livraison (DeliveryType) avant de supprimer celui-ci.
Comme dans le cas de l'annotation @EntityValidator, il est possible de déclarer plusieurs annotations @RemoveValidator en utilisant l'annotation @RemoveValidators. Les validateurs de suppression sont exécutés lors de la suppressions des objets par OpenXava (depuis MapFacade ou avec les actions standards), mais pas en utilisant directement l'API JPA. Si vous souhaitez appliquer une restriction en utilisant l'API JPA, utilisez l'annotation @PreRemove devant votre méthode.
Méthodes de rappel JPA
Avec l'annotation @PrePersist, vous pouvez injecter votre propre logique exécutée juste avant le premier enregistrement de l'objet (que son état devienne persistant dans la base de donnée). Dans cet exemple, chaque fois qu'un type de livraison (DeliveryType) est créé, une description est ajoutée :Comme vous pouvez le constater, la méthode est identique aux autres méthodes, sauf qu'elle exécutée automatiquement juste avant la création de l'objet dans la base de données.
Avec l'annotation @PreUpdate, vous pouvez de la même manière injecter votre logique à exécuter avant l'enregistrement de la mise à jour de l'état de votre objet, c'est-à-dire juste avant l'exécution de l'ordre UPDATE dans la base de données. Dans cet exemple, chaque fois qu'un type de livraison est modifié, un suffixe est ajouté à la description :
Comme vous pouvez le constater, la méthode est identique aux autres méthodes, sauf qu'elle exécutée automatiquement juste avant la modification de l'objet dans la base de données.
Toutes les annotations JPA sont disponibles dans OpenXava : @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate et @PostLoad
Méthodes de rappel OX (à partir de la V4.0.1)
Please, translate this section to FrenchEn utilisant @PreCreate vous pouvez définir une logique qui sera exécutée avant la persistance de l'objet. Vous pouvez ainsi effectuer des opérations de gestion des entités ou des requêtes qui ne sont pas autorisées dans les méthodes de rappel JPA.
Par exemple, si nous voulons créer un client et l'affecter à une facture au cas où aucun client n'aurait été spécifié :
L'opération de gestion de l'entité n'affectera pas le comportement de la méthode de rappel. Tous comme avec @PreCreate, les méthodes possédant les annotations @PostCreate et @PreDelete font partie de la même transaction, ce qui fait que l'utilisation de ces annotations conserve l'intégrité de l'information sans aucun effort supplémentaire de la part du développeur. Combinée aux annotations JPA, l'ordre d'exécution de chaque méthode de rappel est le suivant :
Pour la création d'une entité : @PreCreate, @PrePersist(JPA), @PostPersist(JPA) et @PostCreate.
Pour la suppression d'une entité : @PreDelete, @PreRemove(JPA), @PostRemove(JPA).
Les méthodes possédant ces annotations ne doivent retourner aucune valeur ni accepter aucun paramètre. Ces annotations ont été prévues pour les entités, et sont ignorées lorsqu'utilisées sur les listeners d'entité (entity listeners).
Héritage
OpenXava supporte les héritages Java et JPA. Par exemple, vous pouvez définir une classe annotée avec @MappedSuperclass ainsi :Vous pouvez définir une autre classe annotée @MappedSuperclass qui étend celle ci-dessus :
A partir de maintenant, vous pouvez utiliser les classe Identifiable et Nameable pour définir vos entités, comme ici:
Il est également possible de faire un réel héritage d'une entité vers une autre entité :
Vous pouvez créer un module OpenXava pour les classe Human et Programmer, mais pas Identifiable et Nameable directement. Dans le module Programmer, l'utilisateur n'a accès qu'aux instances de Programmer. Par contre, dans le module Human, l'utilisateur aura accès aux instances de Human. et de Programmer. En outre, si l'utilisateur essaye de voir le détail d'une instance de Programmer depuis le module Human, la vue Programmer sera affichée. Du vrai polymorphisme.
Please, translate to French: Since v4.5 OpenXava supports all inheritance features of JPA, including single table per class hierarchy, joined and table per class mapping strategies, before v4.5 only @AttributeOverrides and single table per class hierarchy mapping strategy was supported.
Clés composites
La méthode conseillée pour définir la clé d'une entité est d'utiliser un identifiant unique autgénéré (avec les annotations @Id et @GeneratedValue), mais parfois, par exemple en travaillant avec des base de données existantes, vous vous trouvez confronté à une entité mise en relation avec une table qui utilise plusieurs colonnes comme clé. Ce cas peut être résolu avec JPA (et donc OpenXava) de deux manières, soit avec l'annotation @IdClass ou avec l'annotation @EmbeddedId.Classe d'identification
Si vous utilisez l'annotation @IdClass dans votre entité pour indiquer une classe clé , vous devez marquer les propriétés clés avec l'annotation @Id :Vous devez également déclarer une classe d'identification, une classe régulière sérialisable contenant toutes les propriétés clés de votre entité :
Id embarqué
Dans cet exemple, une référence vers un objet embarquable (@Embeddable) est définie dans l'entité et annotée @EmbaddedId :Et votre clé est une classe embarquable contenant les propriétés clés de votre entité :
Bean Validaton JSR-303 (new in v4.1)
Please, translate this section to FrenchOpenXava has full support for the standard Java for Bean Validation JSR-303. You can define your own constraints for your entities as explained in Bean Validation specification, and OpenXava will recognize them, showing the corresponding validation messages to the user. Consult the latest Hibernate Validator documentation to learn how to write a JSR-303 validator, since the current version of Hibernate Validator implements JSR-303.
Read the article in JavaLobby about using JSR-303 with OpenXava.
@AssertTrue
Since v4.9 OpenXava allows to inject properties and qualified properties (reference properties) that belong to validated bean, into the message identified by means of the message element of @AssertTrue. Example:In this case we have an @AssertTrue annotating a field of the entity:
{disapproved_driving_test} is the message identifier that is declared under i18n file like this:
disapproved_driving_test=Driver {name} can not be registered: must approve the driving testIf we try to create an entity with name=MIGUEL GRAU and approvedDrivingTest=false the next error message will be shown:Driver MIGUEL GRAU can not be registered: must approved the driving test
In this case we have an @AssertTrue annotating a method of the entity:
{not_roadworthy} is the message identifier that is declared under i18n file like this:
not_roadworthy={type} plate {licensePlate} is not roadworthy. It can not be assigned to the driver {driver.name}If we have an entity: type=AUTO, licensePlate=A1-0001 and roadworthy=false, and try to assign driver (name = MIGUEL GRAU), the validation method will fail and display the error message:AUTO plate A1-0001 is not roadworthy. It can not be assigned to the driver MIGUEL GRAU
Validateur Hibernate (depuis v3.0.1)
OpenXava supporte complétement le validateur Hibernate . Vous pouvez définir vos propres contraintes pour vos entités comme expliqué dans la documentation du validateur Hibernate et OpenXava les reconnaîtra en montrant les messages de validations correspondant à l'utilisateur.En outre, les annotations OpenXava @Required , @PropertyValidator et @EntityValidator sont définies comme des contraintes du validateur Hibernate, ce qui signifie que si vous enregitrez une objet directement avec JPA, ces contraintes s'appliqueront. D'un autre côté, les annotations @RemoveValidator, @PropertyValidator(onlyOnCreate=true), @EntityValidator(onlyOnCreate=true) et la validation par défaut d'OpenXava ne sont pas reconnues par le validateur Hibernate ou JPA, mais seulement par OpenXava.